with MIDI.Control_Events, MIDI.Meta_Events, MIDI.Events;
with MIDI.Traces;
with Storage_Streams; use Storage_Streams;
with Storage_Streams.Unbounded;
with Ada.Text_IO;
with Ada.Containers; use Ada.Containers;
with Ada.Containers.Indefinite_Doubly_Linked_Lists;
procedure MIDI.XMIDI.Convert_To_Midi (
   Filename : String;
   Stream   : not null access Root_Stream_Type'Class
   )
is
   use type Stream_IO.Count;

   XMI             : XMI_File_Type;
   Buffer          : aliased Unbounded.Unbounded_Stream_Type;
   Memory          : not null access Root_Stream_Type'Class := Buffer'Access;

   Timing          : Timing_Type := (True, Beats_Per_Minute => 120);

   procedure Trace (S : String) renames Ada.Text_IO.Put_Line;

   function On_EVNT return Boolean
   is
      At_Time      : Uint32_vl := 0;
      End_Of_Track : Boolean := False;
      List         : Events.Event_Lists.List;

      procedure On_Midi_Event (M : Events.Midi_Event_Type)
      is
         procedure On_Control_Event
         is
            event : Events.Track_Event(M.Token, 0);
         begin
            event.At_Time := At_Time;
            event.Event := M;
            List.Append(event);
            if m.Token in Events.Note_On then
               event.At_Time := At_Time + Uint32_vl'Input(XMI.As_Stream);
               event.Event.As_Note_On.Velocity := 0;
               List.Append(event);
            end if;
         end On_Control_Event;

         procedure On_Meta_Event
         is
            use Meta_Events;
            event : Events.Track_Event(M.Token, Natural(M.As_Meta_Head.Size));
         begin
            event.At_Time := At_Time;
            event.Event := M;
            if event.Size > 0 then
               String'Read(XMI.As_Stream, event.Data);
            end if;
            List.Append(event);
            case M.As_Meta_Head.Command is
               when Commands.End_Of_Track =>
                  End_Of_Track := True;
               when Commands.Tempo =>
                  declare
                     tempo : Meta_Tempo 
                     with Import, Convention => Ada, Address => event.Data'Address;
                  begin
                     Timing.Beats_Per_Minute := To_BPM(Tempo);
                  end;
               when others =>
                  null;
            end case;
         end On_Meta_Event;

         procedure On_SysEx_Event
         is
            event : Events.Track_Event(M.Token, Natural(M.As_SysEx_Size));
         begin
            event.At_Time := At_Time;
            event.Event := M;
            String'Read(XMI.As_Stream, event.Data);
            List.Append(event);
         end On_SysEx_Event;

         procedure On_Other_Event
         is
            event : Events.Track_Event(M.Token, 0);
         begin
            event.At_Time := At_Time;
            event.Event := M;
            List.Append(event);
         end On_Other_Event;
      begin
         case M.Token is
            when Events.Control => On_Control_Event;
            when Events.Meta    => On_Meta_Event;
            when Events.SysEx_Normal | Events.SysEx_Divided => On_SysEx_Event;
            when others         => On_Other_Event;
         end case;
      end On_Midi_Event;

      procedure On_Interval (Interval : Uint8_t)
      is
      begin
         At_Time := At_Time + Uint32_vl(Interval);
      end On_Interval;
   begin
      while not XMI.End_Of_File loop
         exit when End_Of_Track;
         declare
            M : Events.Midi_Event_Type := Events.Midi_Event_Type'Input(XMI.As_Stream);
         begin
            if M.Token < 128 then
               On_Interval(M.Token);
            else
               On_Midi_Event(M);
            end if;
         end;
      end loop;
      At_Time := 0;
      Events.Event_Lists_Sorting.Sort(List);
      declare
         S  : String := "generated by xmi converter";
         m  : Events.Track_Event(Token => 16#FF#, Size => S'Length);
      begin
         m.At_Time := 0;
         m.Event.As_Meta_Head := (Meta_Events.Commands.Text, S'Length);
         m.Data := S;
         List.Prepend(m);
      end;
      for event of List loop
         Uint32_vl'Write(Memory, event.At_Time - At_Time);
         Events.Midi_Event_Type'Output(Memory, event.Event);
         if event.Size > 0 then
            String'Write(Memory, event.Data);
         end if;
         At_Time := event.At_Time;
      end loop;
      return not End_Of_Track;
   end On_EVNT;

   procedure Trace_Buffer 
   is
      EOT : Boolean := False;
   begin
      Buffer.Reset_For_Read;
      while not EOT loop
         declare
            vl : Var_Length_Type := Var_Length_Type'Input(Memory);
            b  : Uint8_t := Uint8_t'Input(Memory);
            c  : Uint8_t := b / 16;
            x  : Uint8_t := b mod 16;
         begin
            Trace("delta_time =>" & To_Uint32(vl)'Img);
            case c is
               when 8 .. 14 =>
                  declare
                     use Control_Events;
                     m : Control_Event(Types'Val(c - 8), x);
                  begin
                     Control_Event'Read(Memory, m);
                     Trace(Traces.Image(m));
                  end;
               when 15 =>
                  case x is
                     when 15 =>
                        declare
                           cmd  : Uint8_t := Uint8_t'Input(Memory);
                           size : Uint32_be := To_Uint32(Var_Length_Type'Input(Memory));
                           s : String(1 .. Natural(size));
                        begin
                           Trace(Traces.To_Hex(b) & ", cmd => " & Traces.To_Hex(cmd) & ", size =>" & size'Img);
                           if s'Length > 0 then
                              String'Read(Memory, s);
                           end if;
                           if cmd = 16#2F# then
                              EOT := True;
                           end if;
                        end;
                     when 0 | 7 =>
                        declare
                           size : Uint32_be := To_Uint32(Var_Length_Type'Input(Memory));
                           s : String(1 .. Natural(size));
                        begin
                           Trace(Traces.To_Hex(b) & ", size =>" & size'Img);
                           if s'Length > 0 then
                              String'Read(Memory, s);
                           end if;
                        end;
                     when others => 
                        Trace(Traces.To_Hex(b) & "[unknown event]");
                  end case;
               when others =>
                  Trace(Traces.To_Hex(b) & "[unknown event]");
            end case;
         end;
      end loop;
   exception
      when Stream_IO.End_Error => null;
   end Trace_Buffer;
begin
   begin
      XMI.Open(Filename);
      XMI.Visit(On_EVNT => On_EVNT'Access);
      XMI.Close;
   exception
      when Stream_IO.End_Error => XMI.Close;
   end;
   Trunk_Head_Type'Write(Stream, ("MThd", 6));
   MThd_Trunk_Type'Write(Stream, (
      Format   => 0, 
      N_Tracks => 1, 
      Tick_Div => To_Tick_Div(Timing)));
   Trunk_Head_Type'Write(Stream, ("MTrk", Uint32_be(Buffer.Length)));
   Buffer.Reset_For_Read;
   Buffer.Copy_To(Stream, Buffer.Length);
end MIDI.XMIDI.Convert_To_MIDI;
